/*
 * ============================================================================
 *                   GNU Lesser General Public License
 * ============================================================================
 *
 *
 *
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 *
 *
 *
 */
package rad.framework.validator;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.FlushModeType;
import javax.persistence.Query;

import rad.framework.jpa.EntityPersistenceContextMap;
import rad.framework.jpa.HibernateUtil;
import rad.framework.jpa.JpaUtil;
import rad.framework.lang.ReflectionUtil;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.validator.Validator;
import org.jboss.seam.Component;

/**
 * @author boudyacho
 */
public class UniqueValidator implements Validator<Unique> {

	protected static final Log log = LogFactory.getLog(UniqueValidator.class);

	private Unique unique;

	public void initialize(Unique unique) {
		this.unique = unique;
	}

	public boolean isValid(Object value) {
		if (value == null)
			return true;

		try {
			return isUnique(value, unique);
		} catch (IllegalStateException e) {
			// e.printStackTrace();
			// must not be in jsf context for the unit test
			return true;
		}
	}

	protected boolean isUnique(Object value, Unique annotation) {
		EntityManager em = getEntityManager(unique, value);

		boolean naturalIdBased = isNaturalIdBased(annotation);
		Map<String, Object> naturalId = null;

		String idName = getIdName(value);
		Object id = getIdValue(value);

		if (naturalIdBased) {
			naturalId = JpaUtil.getNaturalId(value);
			if (!isGeneratedId(value) && isManaged(value, em)) {
				if (naturalId.containsKey(idName)) {
					return true;
				}
			}
		}

		// create where
		StringBuffer where = new StringBuffer();
		Map<String, Object> parameters = new HashMap<String, Object>();

		if (id != null) {
			addParameter(where, parameters, "t", idName, "!=", id);
		}

		if (!naturalIdBased) {
			addParameter(where, parameters, "t", annotation.property(), "=",
					getPropertyValue(value, annotation.property()));
		} else {
			for (String name : naturalId.keySet()) {
				addParameter(where, parameters, "t", name, "=", naturalId
						.get(name));
			}
		}

		String eql = "select count(*) from " + getClass(value) + " t"
				+ where.toString();
		if (log.isDebugEnabled()) {
			log.debug("UniqueValidator EQL: " + eql);
			log.debug("UniqueValidator Parameters: " + parameters);
		}

		Query qry = em.createQuery(eql);

		setParameters(qry, parameters);

		if (!HibernateUtil.isFlushModeManual(em)) {
			em.setFlushMode(FlushModeType.COMMIT);
		}

		Object count = qry.getSingleResult();
		boolean isValid = ((Long) count).intValue() == 0;

		if (log.isDebugEnabled()) {
			log.debug("UniqueValidator Count: " + count);
			if (!isValid) {
				// it is often hard to tell from where the entity is saved
				log.debug("Stack Trace for UniqueValidator Failure:",
						new Exception());
			}
		}
		return isValid;
	}

	// ---------------------------------------------------------------

	protected String getClass(Object value) {
		String aClass = ReflectionUtil.getDeclaringClassAnnotation(value
				.getClass(), UniqueProperties.class);
		if (aClass == null) {
			aClass = ReflectionUtil.getDeclaringClassAnnotation(value
					.getClass(), Unique.class);
		}
		return aClass;
	}

	protected boolean isNaturalIdBased(Unique annotation) {
		// sometimes an entity has more then one property that needs to be
		// unique. the main unique property(ies) should use NaturalId the rest
		// should use Unique.property()
		return StringUtils.isEmpty(annotation.property());
	}

	protected boolean isManaged(Object value, EntityManager em) {
		//log.debug("UniqueValidator Exists: " + HibernateUtil.isExistsInDatabase(em, value)); // TODO remove me
		return HibernateUtil.isExistsInDatabase(em, value);
	}

	// ---------------------------------------------------------------

	protected EntityManager getEntityManager(Unique unique2, Object value) {
		return (EntityManager) Component.getInstance(getPersistenceContextName(
				unique, value));
	}

	protected String getPersistenceContextName(Unique unique, Object value) {
		if (!"entityManager".equals(unique.entityManager())) {
			return unique.entityManager();
		}
		return EntityPersistenceContextMap.instance()
				.getPersistenceContextNameFor(value.getClass());
	}

	// ---------------------------------------------------------------

	protected String getIdName(Object value) {
		return JpaUtil.getIdentityName((Serializable) value);
	}

	protected Object getIdValue(Object value) {
		if (isGeneratedId(value)) {
			return JpaUtil.getIdentity((Serializable) value);
		} else {
			// handle Assigned PK
			return null;
		}
	}

	protected boolean isGeneratedId(Object value) {
		return JpaUtil.isIdentityGenerated(value.getClass());
	}

	// ---------------------------------------------------------------

	protected Object getPropertyValue(Object value, String property) {
		return ReflectionUtil.getProperty(value, property);
	}

	protected Object getPropertyValue(Object value, Method method) {
		return ReflectionUtil.invoke(method, value, new Object[] {});
	}

	// ---------------------------------------------------------------

	protected void addParameter(StringBuffer b, Map<String, Object> parameters,
			String alias, String name, String operator, Object value) {
		if (b.length() == 0) {
			b.append(" where ");
		} else {
			b.append(" and ");
		}
		b.append(alias);
		b.append(".");
		b.append(name);
		b.append(" ");
		b.append(operator);
		b.append(" :");
		b.append(name);
		if ("like".equals(operator)) {
			parameters.put(name, "%" + value + "%");
		} else {
			parameters.put(name, value);
		}
	}

	protected void setParameters(Query qry, Map<String, Object> parameters) {
		for (Iterator i = parameters.keySet().iterator(); i.hasNext();) {
			String key = (String) i.next();
			qry.setParameter(key, parameters.get(key));
		}
	}
}
